﻿using System;
using System.Collections.Generic;
using System.Xml.XPath;
using XPathPlugin.XPathTreeUtils;

namespace XPathPlugin.XPathExpressionUtils
{
    class XPathExpressionParser
    {
        #region Variables

        private const int NOT_FOUND = -1;

        private string _expression;
        private char[] _expressionArray;

        private ParserState _parserState;
        private SyntaxState _symbolState;        

        private XPathNode _xpathTree;               

        private char _symbol;
        private char _symbolBefore;
        private char _symbolNext;        

        char _startStringSymbol;  
        private bool _catchedQuote;

        #endregion

        #region Enums

        private enum ParserState
        {
            Parsing_Union,
            Parsing_Expression,            
        }

        private enum SyntaxState
        {
            Syntax,
            Slash,
            DoubleSlash,
            Union,            
            LeftParenthesis,
            RightParenthesis,
            LeftSquareParenthesis,
            RightSquareParenthesis,
            Comparator,
            Quote,
            WhiteChar,
            String,
            End
        }        

        private enum FilterState
        {
            None,
            Catch_Space,
            Catch_A,
            Catch_AN,
            Catch_AND,
            Catch_AND_,
            Catch_O,
            Catch_OR,
            Catch_OR_,
        }

        private enum ExpressionState
        {
            Single,
            Union,
            Grouping,            
            MultiElement,            
            SingleFilter,
            MultiFilter
        }

        #endregion
        
        public XPathExpressionParser()
        {
            Reset();
        }        

        public XPathExpressionParser(string expression)
            : this()
        {
            if (expression == null)
                throw new PluginException("Incorrect expression");

            _expression = expression;

            try { Initialize(); }
            catch (Exception) { throw new PluginException("Parser exception"); }                         
        }           

        public XPathExpressionParser(XPathExpression expression)
            : this(expression == null ? null : expression.Expression)
        {            
        }

        private XPathExpressionParser(string expression, XPathNode xpathTree)
            : this()
        {
            if (expression == null)
                throw new PluginException("Incorrect expression");

            _expression = expression;

            try { Initialize(xpathTree); }
            catch (Exception) { throw new PluginException("Parser exception");  }
        }

        /// <summary>
        /// Gets built XPathTree
        /// </summary>
        public XPathNode XPathTree
        {
            get { return _xpathTree.Root; }
        }

        /// <summary>
        /// Returns XPathTree representing string
        /// </summary>
        /// <returns>string</returns>
        public override string  ToString()
        {
             return XPathTreeVisualizer.XPathTree2String(XPathTree);
        }

        /// <summary>
        /// Returns XPathTree representing debug string
        /// </summary>
        /// <returns>string</returns>
        public string ToDebugString()
        {
            return XPathTreeVisualizer.XPathTree2String(XPathTree, true);
        }

        /// <summary>
        /// Return only filter of expression.        
        /// </summary>
        /// <param name="expression">expression to process</param>
        /// <returns>string</returns>
        public string GetExpressionFilter(string expression)
        {
            expression = XPathExpressionNormalizer.Normalize(expression);

            if (expression == null || !IsSingleFilterExpression(expression))
                return null;

            int startAttr = IndexOf('[', expression.ToCharArray(), 1);
            int endAttr = LastIndexOf(']', expression.ToCharArray(), 1);
            
            return expression.Substring(startAttr, endAttr - startAttr + 1);
        }

        /// <summary>
        /// Return only filter at specified position
        /// </summary>
        /// <param name="expression">expression to be processed</param>
        /// <param name="position">position of filter, start from 1</param>
        /// <returns>filter</returns>
        public string GetExpressionFilter(string expression, int position)
        {
            if (expression == null || (!IsMultiFilterExpression(expression) && position > 1))
                return null;
            
            int startIndex = -1, endIndex = 0;
            for (int index = 0; index < position; index++)
            {
                startIndex = IndexOf('[', expression.ToCharArray(), ++startIndex, 1);
                
                if (startIndex == NOT_FOUND)
                    return null;
            }

            endIndex = IndexOf(']', expression.ToCharArray(), startIndex, 1);
            return expression.Substring(startIndex, endIndex - startIndex + 1);
        }

        /// <summary>
        /// Return enumerator of all filter in expression
        /// </summary>
        /// <param name="expression">expression to be processed</param>
        /// <returns>filter enumerator</returns>
        public IEnumerator<string> GetExpressionFilters(string expression)
        {
            if (expression == null)
                yield break;

            string filter;
            int position = 1;

            while (true)
            {
                filter = GetExpressionFilter(expression, position++);
                if (filter != null)
                    yield return filter;
                else
                    yield break;
            }
        }

        /// <summary>
        /// Returns filter count
        /// </summary>
        /// <param name="expression">expression to be processed for filter searching</param>
        /// <returns>int</returns>
        public int GetExpressionFilterCount(string expression)
        {
            if (expression == null)
                return NOT_FOUND;

            int count = 0;
            int startIndex = -1;

            while (true)
            {
                startIndex = IndexOf('[', expression.ToCharArray(), ++startIndex, 1);
                
                if (startIndex == NOT_FOUND)
                    break;

                count++;
            }
            
            return count;
        }        

        /// <summary>
        /// Gets expression element only without [, ]
        /// </summary>
        /// <param name="expression">expression to process</param>
        /// <returns>string</returns>
        public string GetExpressionOnly(string expression)
        {
            expression = XPathExpressionNormalizer.Normalize(expression);

            if (expression == null)
                return null;

            else if (!IsSingleFilterExpression(expression))
                return expression;

            else
            {
                string filter = GetExpressionFilter(expression);
                int filterStartIndex = expression.IndexOf(filter, 0);
                return expression.Substring(0, filterStartIndex);
            }
        }

        /// <summary>
        /// Return index of symbol in defined layer.
        /// </summary>
        /// <param name="symbol">symbol to look for</param>
        /// <param name="symbolArray">symbol array where to find</param>
        /// <param name="layer">layer</param>
        /// <returns>int</returns>
        public int IndexOf(char symbol, char[] symbolArray, int layer)
        {
            return IndexOf(symbol, symbolArray, 0, layer);
        }

        /// <summary>
        /// Return index of symbol in defined layer from start index.
        /// </summary>
        /// <param name="symbol">symbol to look for</param>
        /// <param name="symbolArray">symbol array where to find</param>
        /// <param name="startIndex">from which index start searching</param>
        /// <param name="layer">layer</param>
        /// <returns>int</returns>                
        public int IndexOf(char symbol, char[] symbolArray, int startIndex, int layer)
        {
            if (symbolArray == null || layer == 0 || startIndex >= symbolArray.Length)
                return NOT_FOUND;

            Reset();
            
            int index = startIndex - 1;            
            
            // Stack of layers
            Stack<int> stackLayer = new Stack<int>();
            stackLayer.Push(1);

            index = ReadSymbol(++index, symbolArray);
            while(_symbolState != SyntaxState.End) 
            {                
                switch(_symbolState) 
                {                    
                    case SyntaxState.LeftParenthesis:
                    case SyntaxState.LeftSquareParenthesis:
                        if (stackLayer.Peek() == layer && _symbol == symbol)
                            return index;
                        else
                            stackLayer.Push(stackLayer.Peek() + 1);
                        break;
                    
                    case SyntaxState.RightParenthesis:
                    case SyntaxState.RightSquareParenthesis:
                        // Ending parens are in same layer as next text is
                        if (stackLayer.Peek() > 1)
                            stackLayer.Pop();

                        if (stackLayer.Peek() == layer && _symbol == symbol)
                            return index;                   
                                 
                        break;
                    
                    case SyntaxState.DoubleSlash:
                        if (stackLayer.Peek() == layer && _symbol == symbol)
                            return index - 1;
                        break;

                    case SyntaxState.String:                    
                        break;
                    
                    default:
                        if (stackLayer.Peek() == layer && _symbol == symbol)
                            return index;
                        break;
                }

                index = ReadSymbol(++index, symbolArray);
            }

            return NOT_FOUND;
        }

        /// <summary>
        /// Return last index of symbol in defined layer.
        /// </summary>
        /// <param name="symbol">symbol to look for</param>
        /// <param name="symbolArray">symbol array where to find</param>
        /// <param name="layer">layer</param>
        /// <returns>int</returns>
        public int LastIndexOf(char symbol, char[] symbolArray, int layer)
        {
            return LastIndexOf(symbol, symbolArray, 0, layer);
        }

        /// <summary>
        /// Return last index of symbol in defined layer from start index.
        /// </summary>
        /// <param name="symbol">symbol to look for</param>
        /// <param name="symbolArray">symbol array where to find</param>
        /// <param name="startIndex">from which index start searching</param>
        /// <param name="layer">layer</param>
        /// <returns>int</returns>                
        public int LastIndexOf(char symbol, char[] symbolArray, int startIndex, int layer)
        {
            char[] tmpSymbolArray = (char[]) symbolArray.Clone();
            Array.Reverse(tmpSymbolArray);
            int index = IndexOf(symbol, tmpSymbolArray, startIndex, layer);
            
            switch (index)
            {
                case NOT_FOUND:
                    return NOT_FOUND;

                default:
                    return symbolArray.Length - index - 1;
            }
        }

        #region Private Methods

        /// <summary>
        /// Resets parser
        /// </summary>
        private void Reset() 
        {            
            _catchedQuote = false;
            _symbolState = SyntaxState.End;            
            _symbol = _symbolBefore = _symbolNext = _startStringSymbol = '\0';             
        }

        /// <summary>
        /// Initialize parser
        /// </summary>
        private void Initialize()
        {                        
            _expression = XPathExpressionNormalizer.Normalize(_expression);
            _expressionArray = _expression.ToCharArray();            

            _xpathTree = new XPathTree(new XPathValue(_expression, XPathValue.NodeType.Expression));
                        
            Parse();            
        }

        /// <summary>
        /// Internal initialization of parser
        /// </summary>
        /// <param name="tree">tree to set as main tree</param>
        private void Initialize(XPathNode tree)
        {
            _expression = XPathExpressionNormalizer.Normalize(_expression);
            _expressionArray = _expression.ToCharArray();            
            
            // Tree
            _xpathTree = tree;                        
                        
            Parse();            
        }      

        /// <summary>
        /// Reads and process symbol
        /// </summary>
        /// <param name="indexToRead">index where to read</param>
        /// <param name="array">array where to read</param>
        /// <returns>new index</returns>
        private int ReadSymbol(int indexToRead, char[] array)
        {
            _symbolBefore = (array != null && indexToRead > 0 && indexToRead <= array.Length) ? array[indexToRead - 1] : '\0';
            _symbol = (array != null && indexToRead >= 0 && indexToRead < array.Length) ? array[indexToRead] : '\0';
            _symbolNext = (array != null && indexToRead >= 0 && (indexToRead + 1) < array.Length) ? array[indexToRead + 1] : '\0';

            return ProcessSymbol(indexToRead);
        }

        /// <summary>
        /// Process symbol - set symbol state
        /// </summary>
        /// <param name="index">index where the symbol occurs</param>
        /// <returns>int new index</returns>
        private int ProcessSymbol(int index)
        {            
            switch (_symbol)
            {
                case '\0':
                    _symbolState = SyntaxState.End;
                    break;

                case '\'':
                case '"':                   
                    if (_startStringSymbol == '\0')
                    {
                        _startStringSymbol = _symbol;
                        _symbolState = SyntaxState.Quote;
                        _catchedQuote = true;
                    }

                    else if (_startStringSymbol == _symbol)
                    {
                        _startStringSymbol = '\0';
                        _symbolState = SyntaxState.Quote;
                        _catchedQuote = false;
                    }

                    else
                        _symbolState = SyntaxState.String;                                      
                    
                    break;

                case '/':
                    if (_symbolNext == '/')
                    {
                        _symbolState = _catchedQuote ? SyntaxState.String : SyntaxState.DoubleSlash;
                        index += _symbolState == SyntaxState.DoubleSlash ? 1 : 0;
                    }
                    else
                        _symbolState = _catchedQuote ? SyntaxState.String : SyntaxState.Slash;
                    break;

                case '|':
                    _symbolState = _catchedQuote ? SyntaxState.String : SyntaxState.Union;
                    break;

                case '(':
                    _symbolState = _catchedQuote ? SyntaxState.String : SyntaxState.LeftParenthesis;
                    break;

                case ')':
                    _symbolState = _catchedQuote ? SyntaxState.String : SyntaxState.RightParenthesis;
                    break;

                case '[':
                    _symbolState = _catchedQuote ? SyntaxState.String : SyntaxState.LeftSquareParenthesis;
                    break;

                case ']':
                    _symbolState = _catchedQuote ? SyntaxState.String : SyntaxState.RightSquareParenthesis;
                    break;

                case '=':
                    _symbolState = _catchedQuote ? SyntaxState.String : SyntaxState.Comparator;
                    break;

                case '>': // case '>=':
                case '<': // case '<=':
                    if (_symbolNext == '=')
                    {
                        _symbolState = _catchedQuote ? SyntaxState.String : SyntaxState.Comparator;
                        index += _symbolState == SyntaxState.Comparator ? 1 : 0;
                    }
                    _symbolState = _catchedQuote ? SyntaxState.String : SyntaxState.Comparator;
                    break;

                case '!': // case '!='
                    if (_symbolNext == '=')
                    {
                        _symbolState = _catchedQuote ? SyntaxState.String : SyntaxState.Comparator;
                        index += _symbolState == SyntaxState.Comparator ? 1 : 0;
                    }
                    else
                        _symbolState = _catchedQuote ? SyntaxState.String : SyntaxState.Syntax;
                    break;

                case ' ':
                case '\t':
                case '\r':
                case '\n':
                    _symbolState = _catchedQuote ? SyntaxState.String : SyntaxState.WhiteChar;
                    break;

                default:
                    _symbolState = _catchedQuote ? SyntaxState.String : SyntaxState.Syntax;
                    break;
            }
            return index;
        }

        /// <summary>
        /// Parse expression
        /// </summary>
        private void Parse()
        {            
            switch (_parserState)
            {
                case ParserState.Parsing_Union:
                    ParseUnions();
                    break;

                case ParserState.Parsing_Expression:
                    ParseExpressions();
                    break;
            }           
        }

        /// <summary>
        /// Look in expression for uniong symbol at first layer
        /// </summary>
        private void ParseUnions()
        {           
            string value;
            int startIndex = 0;            
            int unionIndex = -1;
            bool breakWhile = false;

            while (true)
            {
                unionIndex = IndexOf('|', _expressionArray, ++unionIndex, 1);
                switch (unionIndex)
                {
                    case NOT_FOUND:
                        // If not found but union reached before
                        if (startIndex > 0)
                        {
                            value = _expression.Substring(startIndex, _expressionArray.Length - startIndex).Trim();
                            _xpathTree.Children.Add(CreateNode(value, XPathValue.NodeType.Expression, "Parsing: Union"));
                        }
                        breakWhile = true;
                        break;

                    default:
                        // If found union
                        value = _expression.Substring(startIndex, unionIndex - startIndex - 1).Trim();
                        _xpathTree.Children.Add(CreateNode(value, XPathValue.NodeType.Expression, XPathValue.JoinType.Union, "Parsing: Union"));
                        startIndex = unionIndex + 1;
                        break;

                }

                if (breakWhile)
                    break;                
            }

            // Change state of parser and parse again
            _parserState = ParserState.Parsing_Expression;
            Parse();
        }

        /// <summary>
        /// Parse expression - search for path delimiters /, //
        /// </summary>
        private void ParseExpressions()
        {
            List<XPathNode> nodeList = new List<XPathNode>();
            
            string expression;
            char[] expressionArray;
            ExpressionState expressionState;
            string value = "";
            int startIndex;
            int expressionIndex;
            int expressionLength;
            bool breakWhile = false;            

            XPathNode childNode;

            // Make children list
            if (_xpathTree.Children.Count > 0)
            {
                foreach (XPathNode node in _xpathTree.Children)
                    nodeList.Add(node);
            }
            else
                nodeList.Add(_xpathTree);            

            foreach (XPathNode node in nodeList)
            {
                // Reset values
                expression = XPathExpressionNormalizer.RemoveNestingParenthesis(node.Value.Value);
                expressionArray = expression.ToCharArray();
                expressionState = GetExpressionState(expression);
                expressionIndex = -1;
                startIndex = 0;
                breakWhile = false;
                value = "";

                // If expression is single - dont process - expression was added
                if (expressionState == ExpressionState.Single)
                    continue;

                // If expression is union expression process union expression - recursively
                else if (expressionState == ExpressionState.Union)
                {
                    new XPathExpressionParser(expression, node);
                    continue;
                }

                while (true)
                {
                    expressionIndex = IndexOf('/', expressionArray, ++expressionIndex, 1);

                    switch (expressionIndex)
                    {
                        case NOT_FOUND:                            
                            value = expression.Substring(startIndex, expressionArray.Length - startIndex).Trim();
                            // If not found but some delimiters reached before
                            if (startIndex > 0)
                            {
                                childNode = CreateNode(value, XPathValue.NodeType.Expression, "Parsing: Expression");
                                node.Children.Add(childNode);
                                ProcessExpression(value, childNode);
                            }
                            // Else if nothing found - proces expression with current node
                            else
                                ProcessExpression(value, node);

                            breakWhile = true;
                            break;

                        default:
                            // If delimiter reached
                            // int correction = _symbolState == SyntaxState.Slash ? 0 : 1;
                            expressionLength = expressionIndex - startIndex;
                            
                            if (expressionLength > 0)
                                value = expression.Substring(startIndex, expressionLength).Trim();
                                                        

                            if (value.Length == 0 || value.Equals("/"))
                                break;
                            
                            startIndex = expressionIndex;                            
                            childNode = CreateNode(value, XPathValue.NodeType.Expression, "Parsing: Expression");
                            node.Children.Add(childNode);                                                       

                            ProcessExpression(value, childNode);
                            break;

                    }

                    if (breakWhile)
                        break;                    
                }
            }
        }

        /// <summary>
        /// Process expression by its state
        /// </summary>
        /// <param name="expression">expression to process</param>
        /// <param name="node">node which expression belongs</param>
        private void ProcessExpression(string expression, XPathNode node)
        {
            if (expression == null || node == null)
                return;
            
            // Normalize expression 
            expression = XPathExpressionNormalizer.Normalize(expression);
            ExpressionState expressionState = GetExpressionState(expression);

            switch (expressionState)
            {
                case ExpressionState.Single:
                    return;

                case ExpressionState.Union:
                case ExpressionState.Grouping:
                case ExpressionState.MultiElement:
                    new XPathExpressionParser(expression, node);
                    break;

                case ExpressionState.MultiFilter:
                    ParseMultiFilter(expression, node);
                    break;

                case ExpressionState.SingleFilter:                    
                    ParseFilter(expression, node, true);
                    break;
            }
        }

        /// <summary>
        /// Parse multifilter expression
        /// </summary>
        /// <param name="expression">expression to parse</param>
        /// <param name="node">node which expression belongs</param>
        private void ParseMultiFilter(string expression, XPathNode node)
        {
            if (expression == null || node == null)
                return;

            string expressionOnly = GetExpressionOnly(expression);
            IEnumerator<string> filters = GetExpressionFilters(expression);
            XPathNode expressionOnlyNode;
            XPathNode filterNode;

            expressionOnlyNode = CreateNode(expressionOnly, XPathValue.NodeType.Expression);
            node.Children.Add(expressionOnlyNode);
            new XPathExpressionParser(expressionOnly, expressionOnlyNode);

            while (filters.MoveNext())
            {
                filterNode = CreateNode(filters.Current, XPathValue.NodeType.Filter, "Parsing: SingleFilter");
                node.Children.Add(filterNode);
                ParseFilter(filters.Current, filterNode, false);
            }
        }        

        /// <summary>
        /// Parse filter - search for filter delimiters and, or
        /// </summary>
        /// <param name="filter">filter to process</param>
        /// <param name="node">node which filter belongs</param>        
        private void ParseFilter(string expression, XPathNode node, bool addExpressionOnly)
        {
            if (expression == null || node == null)
                return;            
            
            List<string> filterList = new List<string>();
            FilterState attrState = FilterState.None;
            Stack<int> stackLayers = new Stack<int>();

            string value = "";
            string filter = GetExpressionFilter(expression);                        
            bool syntaxChar = true;
            bool isLastChar = false;
            char quoteSymbol = '\0';            
            int startIndex = 0;
            int walkingIndex = -1;

            if (addExpressionOnly)
            {
                XPathNode filterNode = CreateNode(filter, XPathValue.NodeType.Filter, "Parsing: Filter");
                XPathNode expressionOnlyNode = CreateNode(GetExpressionOnly(expression), XPathValue.NodeType.Expression, "Parsing: Filter - Expression only");
                
                node.Children.Add(expressionOnlyNode);
                new XPathExpressionParser(expressionOnlyNode.Value.Value, expressionOnlyNode);
                
                node.Children.Add(filterNode);
                node = filterNode;
            }

            filter = filter.Substring(1, filter.Length - 2);
            stackLayers.Push(1);

            // Only first layer attribute
            foreach (char c in filter.ToCharArray())
            {
                ++walkingIndex;
                if (walkingIndex == filter.Length - 1)
                    isLastChar = true;

                // check for char whether is syntax char
                if (c == '\'' || c == '"')
                {
                    if (quoteSymbol == '\0')
                    {
                        quoteSymbol = c;
                        syntaxChar = false;
                    }

                    else if (quoteSymbol == c)
                    {
                        quoteSymbol = '\0';                        
                        syntaxChar = true;
                    }
                }

                // check layers
                if (syntaxChar && c == '[')
                    stackLayers.Push(stackLayers.Peek() + 1);

                // check layers
                else if (syntaxChar && c == ']')
                    stackLayers.Pop();

                // if symbol is syntax char and lies at first layer
                if (syntaxChar && stackLayers.Peek() == 1)
                {
                    switch (c)
                    {
                        case ' ':
                        case '\t':
                        case '\r':
                        case '\n':
                            if (attrState == FilterState.Catch_AND)
                                attrState = FilterState.Catch_AND_;

                            else if (attrState == FilterState.Catch_OR)
                                attrState = FilterState.Catch_OR_;

                            else
                                attrState = FilterState.Catch_Space;
                            break;

                        case 'o':
                        case 'O':
                            if (attrState == FilterState.Catch_Space)
                                attrState = FilterState.Catch_O;
                            else
                                attrState = FilterState.None;
                            break;

                        case 'r':
                        case 'R':
                            if (attrState == FilterState.Catch_O)
                                attrState = FilterState.Catch_OR;
                            else
                                attrState = FilterState.None;
                            break;

                        case 'a':
                        case 'A':
                            if (attrState == FilterState.Catch_Space)
                                attrState = FilterState.Catch_A;
                            else
                                attrState = FilterState.None;
                            break;

                        case 'n':
                        case 'N':
                            if (attrState == FilterState.Catch_A)
                                attrState = FilterState.Catch_AN;
                            else
                                attrState = FilterState.None;
                            break;

                        case 'd':
                        case 'D':
                            if (attrState == FilterState.Catch_AN)
                                attrState = FilterState.Catch_AND;
                            else
                                attrState = FilterState.None;
                            break;

                        default:
                            attrState = FilterState.None;
                            break;
                    }

                    switch (attrState)
                    {
                        // Process and delimiter
                        case FilterState.Catch_AND_:
                            value = filter.Substring(startIndex, walkingIndex - startIndex - 4).Trim();
                            node.Children.Add(CreateNode(value, XPathValue.NodeType.FilterInside, XPathValue.JoinType.And, "Parsing: FilterInside"));
                            startIndex = walkingIndex + 1;
                            break;

                        // Process or delimiter
                        case FilterState.Catch_OR_:
                            value = filter.Substring(startIndex, walkingIndex - startIndex - 3).Trim();
                            node.Children.Add(CreateNode(value, XPathValue.NodeType.FilterInside, XPathValue.JoinType.Or, "Parsing: FilterInside"));
                            startIndex = walkingIndex + 1;
                            break;
                    }

                    // process end char
                    if (isLastChar && startIndex > 0)
                    {
                        value = filter.Substring(startIndex, walkingIndex - startIndex + 1);
                        node.Children.Add(CreateNode(value, XPathValue.NodeType.FilterInside, "Parsing: FilterInside"));
                    }
                }
            }                        
        }        

        /// <summary>
        /// Returns whether expression is single - no union, no multielement, no grouping, no filter
        /// </summary>
        /// <param name="expression">expression to process</param>
        /// <returns>bool</returns>
        private bool IsSingleExpression(string expression)
        {            
            return expression == null || 
                (expression != null && !IsGroupingExpression(expression) && !IsUnionExpression(expression) && !IsMultiElementExpression(expression) && !IsSingleFilterExpression(expression));
        }

        /// <summary>
        /// Returns whether expression is grouping - starts with ( and ends with )
        /// </summary>
        /// <param name="expression">expression to process</param>
        /// <returns>bool</returns>
        private bool IsGroupingExpression(string expression)
        {
            return expression != null && expression.StartsWith("(") && expression.EndsWith(")");
        }

        /// <summary>
        /// Returns whether expression is union - at first layer contains union symbol
        /// </summary>
        /// <param name="expression">expression to process</param>
        /// <returns>bool</returns>
        private bool IsUnionExpression(string expression)
        {
            if (expression == null)
                return false;

            return IndexOf('|', expression.ToCharArray(), 1) >= 0;
        }

        /// <summary>
        /// Returns whether expression is multielement
        /// </summary>
        /// <param name="expression">expression to process</param>
        /// <returns>bool</returns>
        private bool IsMultiElementExpression(string expression)
        {
            if (expression == null)
                return false;

            if (expression.StartsWith("//"))
                return IndexOf('/', expression.ToCharArray(), 2, 1) > 0;

            else if (expression.StartsWith("/"))
                return IndexOf('/', expression.ToCharArray(), 1, 1) > 0;

            else
                return IndexOf('/', expression.ToCharArray(), 0, 1) > 0;
        }

        /// <summary>
        /// Returns whether expression is single filter - no multielement, ends with ]
        /// </summary>
        /// <param name="expression">expression to process</param>
        /// <returns>bool</returns>
        private bool IsSingleFilterExpression(string expression)
        {
            return expression != null && !IsMultiElementExpression(expression) && expression.EndsWith("]");
        }

        /// <summary>
        /// Returns whether expression is multi filter - no multielement, only multifilter
        /// </summary>
        /// <param name="expression">expression to process</param>
        /// <returns>bool</returns>
        private bool IsMultiFilterExpression(string expression)
        {
            if (expression == null || IsMultiElementExpression(expression))
                return false;

            int rightSquareParensIndex = IndexOf('[', expression.ToCharArray(), 1);

            return rightSquareParensIndex > 0 && IndexOf('[', expression.ToCharArray(), ++rightSquareParensIndex, 1) > 0;
        }        

        /// <summary>
        /// Gets the expression state
        /// </summary>
        /// <param name="expression">expression to process</param>
        /// <returns>expression state</returns>
        private ExpressionState GetExpressionState(string expression)
        {
            if (expression == null)
                return ExpressionState.Single;

            else if (IsGroupingExpression(expression))
                return ExpressionState.Grouping;

            else if (IsUnionExpression(expression))
                return ExpressionState.Union;

            else if (IsMultiElementExpression(expression))
                return ExpressionState.MultiElement;

            else if (IsMultiFilterExpression(expression))
                return ExpressionState.MultiFilter;

            else if (IsSingleFilterExpression(expression))
                return ExpressionState.SingleFilter;
                        
            else
                return ExpressionState.Single;

        }

        /// <summary>
        /// Creates new XPathNode
        /// </summary>
        /// <param name="value">expression</param>
        /// <param name="nodeType">node type</param>
        /// <returns>new XPathNode</returns>
        private static XPathNode CreateNode(string value, XPathValue.NodeType nodeType)
        {
            return new XPathNode(new XPathValue(value, nodeType));
        }

        /// <summary>
        /// Creates new XPathNode
        /// </summary>
        /// <param name="value">expression</param>
        /// <param name="nodeType">node type</param>
        /// <param name="debug">debug string</param>
        /// <returns>new XPathNode</returns>
        private static XPathNode CreateNode(string value, XPathValue.NodeType nodeType, string debug)
        {
            return new XPathNode(new XPathValue(value, nodeType, debug));
        }

        /// <summary>
        /// Creates new XPathNode
        /// </summary>
        /// <param name="value">expression</param>
        /// <param name="nodeType">node type</param>
        /// <param name="joinType">join type</param>
        /// <param name="debug">debug string</param>
        /// <returns>XPathNode</returns>
        private static XPathNode CreateNode(string value, XPathValue.NodeType nodeType, XPathValue.JoinType joinType, string debug)
        {
            return new XPathNode(new XPathValue(value, nodeType, joinType, debug));
        }
        
        #endregion
    }
}
